// Last updated: 2010-05-11

#include <iostream>
#include <fstream>
#include <string>

#include "invert.h"
#include "ppm.h"
#include "Vector.h"
#include "Light.h"
#include "Sphere.h"
#include "Scene.h"
#include "Ray.h"
#include "Color.h"

#include "omp.h"

using namespace std;

#define NUM_SAMPLES 16

// FUNCTION PROTOTYPES //
void writePPM(Scene* myScene);
/////////////////////////

//Returns a pointer to the scene specified by the given file name.
//Is null if a parsing error occurs.
Scene* parseFile(char* filename){
	ifstream inputFile(filename);
	if(!inputFile){
		cout << "Could not open input file." << endl;
		return NULL;
	}
	inputFile.exceptions(ifstream::eofbit | ifstream::failbit);
	Scene * myScene = NULL;
	try{
		myScene = new Scene(inputFile);
	}
	catch(std::ifstream::failure e){
		cout << "Unexpected end of file." << endl;
		myScene = NULL;
	}
	inputFile.close();
	return myScene;
}

int main(int argc, char *argv[])
{
	if(argc < 2){
		return 1;
	}
	Scene* testScene = parseFile(argv[1]);
	if(testScene == NULL){
		return 1;
	}
	writePPM(testScene);
	delete testScene;
	return 0;
}

void writePPM(Scene* myScene){
	// frontmost primitive index array - Stores the indices of the primitive closest to the viewport
	// stores -1 if there is no such primitive

	int *primIndices;
	primIndices = new int [myScene->yRes*myScene->xRes];
	for (int i = 0; i < (myScene->yRes * myScene->xRes); i++)
		primIndices[i] = -1;
	
	// pointer to pixel data - You have to store your ray traced pixels here.  
    //  NOTE: pixels[0] is the top-left of the image and 
	//  pixels[3*width*height-1] is the bottom-right of the image.

	unsigned char *pixels; 
    pixels = new unsigned char [3*myScene->yRes*myScene->xRes];
	
	// useful constants to avoid recalculation
	double leftRightConst = (myScene->right - myScene->left) / (myScene->xRes - 1);
	double bottomTopConst = (myScene->bottom - myScene->top) / (myScene->yRes - 1);

	//Main raytracing loop
	Vector origin = Vector(0.0, 0.0, 0.0, 1.0);
#pragma omp parallel for
	for(int i = 0; i < myScene->yRes; i++){
		for (int k = 0; k < myScene->xRes; k++){
			Vector currentDirection = Vector(myScene->left + leftRightConst * k,
				myScene->top + bottomTopConst * i, -myScene->near, 0.0);
			int itemFound = -1;
			Ray currentRay = Ray(origin, currentDirection, 1.0, 0);
			Color pixelColor = currentRay.trace(myScene, itemFound);
			primIndices[i*myScene->xRes+k] = itemFound;
			
			pixels[3 * (i*myScene->xRes+k)] = pixelColor.getR();
			pixels[3 * (i*myScene->xRes+k) + 1] = pixelColor.getG();
			pixels[3 * (i*myScene->xRes+k) + 2] = pixelColor.getB();
		}
	}
	
	// begin super sampling section
#pragma omp parallel for
	for(int i = 0; i < myScene->yRes; i++){
		for (int k = 0; k < myScene->xRes; k++){
			int itemFound = primIndices[i * myScene->xRes + k];
			Color pixelColor;
			
			// get index of primitives on the four sides of current pixel
			int leftPrim = (k == 0)? itemFound : primIndices[i * myScene->xRes + k - 1];
			int topPrim = (i == 0)? itemFound : primIndices[(i - 1) * myScene->xRes + k];
			int rightPrim = (k == myScene->xRes - 1)? itemFound : primIndices[i * myScene->xRes + k + 1];
			int botPrim = (i == myScene->yRes - 1)? itemFound : primIndices[(i + 1) * myScene->xRes + k];	
			// if primitive indices are different, this is a boundary, apply supersampling
			if (leftPrim != itemFound || topPrim != itemFound || rightPrim != itemFound || botPrim != itemFound)
			{
				int neighborIndex = 0;
				Color neighborColors[NUM_SAMPLES];
				
				double lim = sqrt(NUM_SAMPLES) - 1;
				for (double n = -lim/2.0; n <= lim/2.0; n += 1.0)
				{
					for (double m = -lim/2.0; m <= lim/2.0; m += 1.0)
					{
						Vector neighborVector = Vector(myScene->left + leftRightConst * (k + m / lim),
													   myScene->top + bottomTopConst * (i + n / lim), -myScene->near, 0.0);
						int temp = -1;
						Ray neighborRay = Ray(origin, neighborVector, 1.0, 0);
						neighborColors[neighborIndex] = neighborRay.trace(myScene, temp);
						neighborIndex++;
					}
				}
				
				// average the color across the pixel and its neighbors
				double avgRGB[3] = {0, 0, 0};
				for (int c = 0; c < NUM_SAMPLES; c++)
				{
					avgRGB[0] += neighborColors[c].r;
					avgRGB[1] += neighborColors[c].g;
					avgRGB[2] += neighborColors[c].b;
				}
				
				// update pixelColor
				pixelColor = Color(avgRGB[0]/NUM_SAMPLES, avgRGB[1]/NUM_SAMPLES, avgRGB[2]/NUM_SAMPLES);
				
				pixels[3 * (i*myScene->xRes+k)] = pixelColor.getR();
				pixels[3 * (i*myScene->xRes+k) + 1] = pixelColor.getG();
				pixels[3 * (i*myScene->xRes+k) + 2] = pixelColor.getB();
			}
		}
	}
	// end supersampling section
	// save_image is the function you will use to create the output .ppm file.
	save_image(myScene->xRes, myScene->yRes, myScene->outputFilename.c_str(), pixels);
}

